package com.catl.plc.handler;

import com.catl.plc.master.AbstractPlcMaster;
import com.catl.plc.meta.RegisterTypeEnums;
import com.catl.plc.utils.CollectionUtils;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.msg.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

public class Modbus4jUtil {
    private static final Logger log = LoggerFactory.getLogger(Modbus4jUtil.class);

    private Modbus4jUtil() {
    }

    public static List<Integer> read(AbstractPlcMaster master, RegisterTypeEnums type, int slaveId, int startOffset, int length) {
        long startTime = System.currentTimeMillis();
        List<Integer> list = new ArrayList<>();
        try {
            switch (type) {
                case H00:
                    list = readCoils(master.getModbus4jMaster(), slaveId, startOffset, length);
                    break;
                case H01:
                    list = readDiscreteInputs(master.getModbus4jMaster(), slaveId, startOffset, length);

                    break;
                case H03:
                    list = readInputRegisters(master.getModbus4jMaster(), slaveId, startOffset, length);
                    break;
                case H04:
                    list = readHoldingRegisters(master.getModbus4jMaster(), slaveId, startOffset, length);
                    break;
                default:
                    throw new IllegalArgumentException("无效的plc读写类型！！！");
            }
            master.getOkHandler().accept(list);
        } catch (Exception e) {
            master.getErrorHandler().accept(e);
            log.error("modbus 异常:{}", e);
        }
        log.warn("[plc读取] >>> 从几号:{},起始偏移量:{},读取结果:{},耗时:{},读取数据:{},类型:{}",
                slaveId, startOffset, !CollectionUtils.isEmpty(list), (System.currentTimeMillis() - startTime)/1000.0, list, type.getName());
        return list;
    }

    public static boolean write(AbstractPlcMaster master, RegisterTypeEnums type, int slaveId, int startOffset, List<Integer> commands) {
        long startTime = System.currentTimeMillis();
        boolean result = false;
        try {
            switch (type) {
                case H00:
                    result = writeCoils(master.getModbus4jMaster(), slaveId, startOffset, numberToBool(commands));
                    break;
                case H04:
                    result = writeHoldingRegisters(master.getModbus4jMaster(), slaveId, startOffset, numberToShort(commands));

                    break;
                case H05:
                    result = writeCoil(master.getModbus4jMaster(), slaveId, startOffset, numberToBool(commands)[0]);
                    break;
                case H06:
                    result = writeHoldingRegister(master.getModbus4jMaster(), slaveId, startOffset, numberToShort(commands)[0]);
                    break;
                default:
                    throw new IllegalArgumentException("无效的plc读写类型！！！");
            }
            master.getOkHandler().accept(result);
        } catch (Exception e) {
            master.getErrorHandler().accept(e);
            log.error("modbus 异常:{}", e);
        }
        log.warn("[plc写入] >>> 从几号:{},起始偏移量:{},写入结果:{},耗时:{},写入数据:{},类型:{}",
                slaveId, startOffset, result, (System.currentTimeMillis() - startTime)/1000.0, commands, type.getName());
        return result;
    }

    /**
     * 读（线圈）开关量数据，相当于功能码：0x-读线圈状态
     *
     * @params: [ip, slaveId, offset, numberOfRegister]
     * @return: boolean[]
     * @throws:
     */
    public static List<Integer> readCoils(ModbusMaster master, int slaveId, int offset, int numberOfRegister) throws ModbusTransportException {
        boolean[] result = new boolean[0];
        ReadCoilsRequest request = new ReadCoilsRequest(slaveId, offset, numberOfRegister);
        ReadCoilsResponse response = (ReadCoilsResponse) master.send(request);
        if (response.isException()) {
            log.error("readCoilStatus response: message={}" ,response.getExceptionMessage());
        } else {
            result = response.getBooleanData();
        }
        return boolToNumber(result);

    }

    /**
     * 读取外围设备输入的开关量，相当于功能码：1x-读离散输入状态
     *
     * @params: [ip, offset, numberOfRegister]
     * @return: boolean[]
     * @throws:
     */
    public static List<Integer> readDiscreteInputs(ModbusMaster master, int slaveId, int offset, int numberOfRegister) throws ModbusTransportException {
        boolean[] result = new boolean[0];
        ReadDiscreteInputsRequest request = new ReadDiscreteInputsRequest(slaveId, offset, numberOfRegister);
        ReadDiscreteInputsResponse response = (ReadDiscreteInputsResponse) master.send(request);
        if (response.isException()) {
            log.error("readInputStatus response: message={}" ,response.getExceptionMessage());
        } else {
            result = response.getBooleanData();
        }
        return boolToNumber(result);
    }

    /**
     * 读取保持寄存器数据，相当于功能码：4x-读保持寄存器
     *
     * @params: [ip, offset, numberOfRegister]
     * @return: short[]
     * @throws:
     */
    public static List<Integer> readHoldingRegisters(ModbusMaster master, int slaveId, int offset, int numberOfRegister) throws ModbusTransportException {
        short[] result = new short[0];
        ReadHoldingRegistersRequest request = new ReadHoldingRegistersRequest(slaveId, offset, numberOfRegister);
        ReadHoldingRegistersResponse response = (ReadHoldingRegistersResponse) master.send(request);
        if (response.isException()) {
            log.error("readHoldingRegister response: message={}" ,response.getExceptionMessage());
        } else {
            result = response.getShortData();
        }
        return shortToNumber(result);
    }

    /**
     * 读取外围设备输入的数据，相当于功能码：3x-读输入寄存器
     *
     * @params: [ip, offset, numberOfRegister]
     * @return: short[]
     * @throws:
     */
    public static List<Integer> readInputRegisters(ModbusMaster master, int slaveId, int offset, int numberOfRegister) throws ModbusTransportException {
        short[] result = new short[0];
        ReadInputRegistersRequest request = new ReadInputRegistersRequest(slaveId, offset, numberOfRegister);
        ReadInputRegistersResponse response = (ReadInputRegistersResponse) master.send(request);

        if (response.isException()) {
            log.error("readInputRegisters response: message={}" ,response.getExceptionMessage());
        } else {
            result = response.getShortData();
        }
        return shortToNumber(result);
    }


//---------------------------------------------------------------------------------------------------

    /**
     * 写多个开关量数据（线圈），相当于功能码：0FH-写多个线圈
     *
     * @params: [ip, startOffset, data]
     * @return: boolean
     * @throws:
     */
    public static boolean writeCoils(ModbusMaster master, int slaveId, int startOffset, boolean[] data) throws ModbusTransportException {
        boolean result = false;
        WriteCoilsRequest request = new WriteCoilsRequest(slaveId, startOffset, data);
        WriteCoilsResponse response = (WriteCoilsResponse) master.send(request);

        if (response.isException()) {
            log.error("writeCoils response: message={}" ,response.getExceptionMessage());
        } else {
            result = !response.isException();
        }
        return result;
    }

    /**
     * 写多个保持寄存器，相当于功能码：10H-写多个保持寄存器
     *
     * @params: [ip, slaveId, startOffset, data]
     * @return: boolean
     * @throws:
     */
    public static boolean writeHoldingRegisters(ModbusMaster master, int slaveId, int startOffset, short[] data) throws ModbusTransportException {
        boolean result = false;
        WriteRegistersRequest request = new WriteRegistersRequest(slaveId, startOffset, data);
        WriteRegistersResponse response = (WriteRegistersResponse) master.send(request);
        if (response.isException()) {
            log.error("writeHoldingRegisters response: message={}", response.getExceptionMessage());
        } else {
            result = !response.isException();
        }
        return result;
    }

    /**
     * 写单个（线圈）开关量数据，相当于功能码：05H-写单个线圈
     *
     * @params: [ip, writeOffset, writeValue]
     * @return: boolean
     * @throws:
     */
    public static boolean writeCoil(ModbusMaster master, int slaveId, int writeOffset, boolean writeValue) throws ModbusTransportException {
        boolean result = false;
        WriteCoilRequest request = new WriteCoilRequest(slaveId, writeOffset, writeValue);
        WriteCoilResponse response = (WriteCoilResponse) master.send(request);
        if (response.isException()) {
            log.error("writeCoil response: message={}", response.getExceptionMessage());
        } else {
            result = !response.isException();
        }
        return result;
    }

    /**
     * 写单个保持寄存器，相当于功能码：06H-写单个保持寄存器
     *
     * @params: [slaveId, writeOffset, writeValue]
     * @return: boolean true 成功  false 失败
     * @throws:
     */
    public static boolean writeHoldingRegister(ModbusMaster master, int slaveId, int writeOffset, short writeValue) throws ModbusTransportException {
        boolean result = false;
        WriteRegisterRequest request = new WriteRegisterRequest(slaveId, writeOffset, writeValue);
        WriteRegisterResponse response = (WriteRegisterResponse) master.send(request);
        if (response.isException()) {
            log.error("writeHoldingRegister response: message={}" , response.getExceptionMessage());
        } else {
            result = !response.isException();
        }
        return result;

    }
    private static boolean[] numberToBool(List<? extends Number> list) {
        if (CollectionUtils.isEmpty(list)) {
            return new boolean[0];
        }
        boolean[] booleans = new boolean[list.size()];
        for (int i = 0; i < list.size(); i++) {
            booleans[i] = list.get(i).intValue() == 1;
        }
        return booleans;
    }

    private static short[] numberToShort(List<? extends Number> list) {
        if (CollectionUtils.isEmpty(list)) {
            return new short[0];
        }
        short[] shorts = new short[list.size()];
        for (int i = 0; i < list.size(); i++) {
            shorts[i] = list.get(i).shortValue();
        }
        return shorts;
    }

    private static List<Integer> boolToNumber(boolean[] booleans) {
        List<Integer> list = new ArrayList<>();
        if (null == booleans || booleans.length == 0) {
            return list;
        }
        for (int i = 0; i < booleans.length; i++) {
            list.add(booleans[i] ? 1 : 0);
        }
        return list;
    }

    private static List<Integer> shortToNumber(short[] shorts) {
        List<Integer> list = new ArrayList<>();
        if (null == shorts || shorts.length == 0) {
            return list;
        }
        for (int i = 0; i < shorts.length; i++) {
            list.add((int) shorts[i]);
        }
        return list;
    }
}
